/**
 * 字符类，包含一个字符相关的信息
 * 1. 唯一id
 * 2. 字符内容
 * 3. 字符是否被删除
 */
class CrdtChar {
  constructor(id, value, deleted = false) {
    this.id = id; // 唯一id
    this.value = value; // 字符内容
    this.deleted = deleted; // 是否被删除，true表示被删除，false表示未删除
  }
}

/**
 * CRDT文本类，包含一个文本相关的信息
 */
class CrdtText {
  /**
   *
   * @param {*} clientId 客户端id，也就是副本id
   */
  constructor(clientId) {
    this.clientId = clientId; // 客户端id
    this.chars = []; // 存储所有的字符串节点，注意每一个字符串节点都是一个CrdtChar的实例对象
    this.counter = 0; // 本地的自增计数器，确保生成的字符id是唯一的
  }

  // 生成全局的唯一id [counter, clientId]
  // counter是本地的自增计数器，clientId是客户端id
  // 这样生成的id是唯一的
  _genId() {
    return [this.counter++, this.clientId];
  }

  /**
   * 接收两个id，比较两个id的大小
   * @param {*} id1
   * @param {*} id2
   */
  static compare(id1, id2) {
    // 先进行 counter 的比较
    if (id1[0] !== id2[0]) return id1[0] - id2[0]; // 先比较 counter 的值，小的在前面
    return id2[1].localeCompare(id1[1]); // 如果 counter 相同，则比较 clientId 的值
    // 这里的 localeCompare 是属于字符串的方法，专门用于比较两个字符串
    // 如果两个字符串相同，则返回 0
    // 如果第一个字符串小于第二个字符串，也就是在 ASCII 码中，第一个字符串排在第二个字符串前面，则返回 -1
    // 如果第一个字符串大于第二个字符串，也就是在 ASCII 码中，第一个字符串排在第二个字符串后面，则返回 1
  }

  /**
   * 判断两个字符的 id 是否完全相同
   * @param {*} id1
   * @param {*} id2
   */
  static equalId(id1, id2) {
    return id1[0] === id2[0] && id1[1] === id2[1]; // counter 和 clientId 都相同
  }

  /**
   * 返回当前的 text 文本中所有字符的集合
   * 注意不包含被删除的字符
   */
  value() {
    return this.chars
      .filter((c) => !c.deleted) // 过滤掉被删除的字符
      .map((c) => c.value) // 提取字符的值
      .join(""); // 将所有的字符连接成一个字符串
  }

  // 接下来完成插入和删除方法
  // 无论是插入还是删除，都分为本地操作和远程操作

  /**
   * 本地插入和远程插入的公共逻辑
   * @param {*} char 接收一个要插入的字符，注意这里仍然是一个CrdtChar的实例对象
   */
  _insertCharSoted(char) {
    // 首先需要找到插入的字符应该插入在哪一个位置
    const idx = this.chars.findIndex(
      (c) => CrdtText.compare(char.id, c.id) < 0
    ); // 找到第一个比当前字符大的字符的索引

    if (idx === -1) {
      // 进入此分支，说明当前的字符就是最大，没有比它更大的
      // 直接插入到最后
      this.chars.push(char);
    } else {
      // 进入此分支，说明当前的字符不是最大
      // 直接插入到指定的 idx 位置
      this.chars.splice(idx, 0, char); // 在指定的 idx 位置插入当前的字符
    }
  }

  /**
   * 本地插入
   */
  localInsert(index, value) {
    // 首先生成一个全新的全局唯一id
    const id = this._genId(); // 生成一个全局唯一id
    // 然后创建一个新的字符对象
    const char = new CrdtChar(id, value); // 创建一个新的 CrdtChar 字符对象
    // 然后将字符插入到指定的位置
    this._insertCharSoted(char); // 将字符插入到指定的位置
    // 然后返回当前的字符对象
    return char;
  }

  /**
   * 远程插入
   * remoteChar 是一个 CrdtChar 的实例对象
   */
  remoteInsert(remoteChar) {
    // 远程插入的逻辑和本地插入的逻辑是一样的
    // 只不过这里的字符是远程传过来的
    // 直接调用本地插入的方法即可
    this._insertCharSoted(remoteChar); // 将字符插入到指定的位置
  }

  /**
   * 本地删除
   */
  localDelete(index) {
    // 1. 找到对应 index 的字符对象
    const char = this.chars[index];
    if (char) char.deleted = true; // 2. 将字符对象的 deleted 属性设置为 true
    // 3. 返回被删除的字符对象
    return char;
  }

  /**
   * 远程删除
   */
  remoteDelete(id) {
    // 1. 首先根据 id 找到对应的字符对象
    const char = this.chars.find((c) => CrdtText.equalId(c.id, id));
    // 2. 找到之后，仍然是做一个标记即可
    if (char) char.deleted = true; // 3. 将字符对象的 deleted 属性设置为 true
  }
}

module.exports = {
  CrdtChar,
  CrdtText,
};
